/* captest.c
**
** Copyright 2011, The Android Open Source Project
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
**     * Redistributions of source code must retain the above copyright
**       notice, this list of conditions and the following disclaimer.
**     * Redistributions in binary form must reproduce the above copyright
**       notice, this list of conditions and the following disclaimer in the
**       documentation and/or other materials provided with the distribution.
**     * Neither the name of The Android Open Source Project nor the names of
**       its contributors may be used to endorse or promote products derived
**       from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
** DAMAGE.
*/

#include <tinyalsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <cutils/properties.h>
#include <sys/reboot.h>
#include <cutils/android_reboot.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/wait.h>
#include "cutils/log.h"

#ifdef LOG_TAG
#undef LOG_TAG
#define LOG_TAG "captest"
#endif

#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT  0x20746d66
#define ID_DATA 0x61746164

#define FORMAT_PCM 1

#define PCM_TEST "/sdcard/pcm-test"
#define REPEAT_OVER        0x2
#define REPEAT_CONTINUE    0x4

#define DEBUG
#ifdef DEBUG
#define dbgprint(format,args...) ({ \
        fprintf (stderr, "[%s] <%d>-->", __func__, __LINE__); \
        fprintf(stderr, format, ##args);})
#else
#define dbgprint(format,args...)
#endif

#define AEC_PLAY

// -D 1 -d 0 -r 64000 -c 2 -b 24
unsigned int card = 1;
unsigned int device = 0;
unsigned int channels = 8;
unsigned int rate = 16000;
unsigned int bits = 24;
unsigned int period_size = 1024;
unsigned int period_count = 4;
unsigned int reboot_times = 0;
unsigned int repeat_times = 1;
unsigned int times_seconds = 0;

unsigned int raw_reboot = 0;
unsigned int raw_repeat = 3;

struct timeval tpstart, tpend;
int timeuse = -1;

pthread_cond_t pcap_start = PTHREAD_COND_INITIALIZER;
pthread_mutex_t timeout_lock = PTHREAD_MUTEX_INITIALIZER;

struct wav_header {
	uint32_t riff_id;
	uint32_t riff_sz;
	uint32_t riff_fmt;
	uint32_t fmt_id;
	uint32_t fmt_sz;
	uint16_t audio_format;
	uint16_t num_channels;
	uint32_t sample_rate;
	uint32_t byte_rate;
	uint16_t block_align;
	uint16_t bits_per_sample;
	uint32_t data_id;
	uint32_t data_sz;
};

int capturing = 1;

typedef enum { false, true } bool;

static int rm_dir(const char *path)
{
	struct dirent *dir;
	struct stat st;
	char sub_path[64];

	DIR *dirp = opendir(path);
	if (!dirp) {
		return -1;
	}

	while ((dir = readdir(dirp)) != NULL) {
		if (strcmp(dir->d_name, ".") == 0
		    || strcmp(dir->d_name, "..") == 0) {
			continue;
		}
		sprintf(sub_path, "%s/%s", path, dir->d_name);
		if (lstat(sub_path, &st) == -1) {
			continue;
		}
		if (S_ISDIR(st.st_mode)) {
			if (rm_dir(sub_path) == -1) {
				closedir(dirp);
				return -1;
			}
			rmdir(sub_path);
		} else if (S_ISREG(st.st_mode)) {
			unlink(sub_path);
		} else {
			continue;
		}
	}
	if (rmdir(path) == -1) {
		closedir(dirp);
		return -1;
	}

	closedir(dirp);
	return 0;
}

static void setenv_to_property()
{
	char tmp[PROPERTY_VALUE_MAX];

	ALOGD("\n");
	sprintf(tmp, "%d", card);
	property_set("persist.sys.capcard", tmp);

	sprintf(tmp, "%d", device);
	property_set("persist.sys.capdevice", tmp);

	sprintf(tmp, "%d", channels);
	property_set("persist.sys.capchannels", tmp);

	sprintf(tmp, "%d", rate);
	property_set("persist.sys.caprate", tmp);

	sprintf(tmp, "%d", bits);
	property_set("persist.sys.capbit", tmp);

	sprintf(tmp, "%d", reboot_times);
	property_set("persist.sys.reboot_times", tmp);

	sprintf(tmp, "%d", repeat_times);
	property_set("persist.sys.repeat_times", tmp);

	sprintf(tmp, "%d", times_seconds);
	property_set("persist.sys.times_seconds", tmp);
}

static void getenv_from_property()
{
	int ret = -1;
	char tmp[PROPERTY_VALUE_MAX];

	ALOGD("\n");
	ret = property_get("persist.sys.capcard", tmp, NULL);
	if (ret) {
		card = atoi(tmp);
	} else {
		//default
	}

	ret = property_get("persist.sys.capdevice", tmp, NULL);
	if (ret) {
		device = atoi(tmp);
	} else {
		//default
	}

	ret = property_get("persist.sys.capchannels", tmp, NULL);
	if (ret) {
		channels = atoi(tmp);
	} else {
		//default
	}

	ret = property_get("persist.sys.caprate", tmp, NULL);
	if (ret) {
		rate = atoi(tmp);
	} else {
		//default
	}

	ret = property_get("persist.sys.capbit", tmp, NULL);
	if (ret) {
		bits = atoi(tmp);
	} else {
		//default
	}

	ret = property_get("persist.sys.reboot_times", tmp, NULL);
	if (ret) {
		reboot_times = atoi(tmp);
	} else {
		//default
	}

	ret = property_get("persist.sys.repeat_times", tmp, NULL);
	if (ret) {
		repeat_times = atoi(tmp);
	} else {
		//default
	}

	ret = property_get("persist.sys.times_seconds", tmp, NULL);
	if (ret) {
		times_seconds = atoi(tmp);
	} else {
		//default
	}

}

static void check_cmdline(int argc, char **argv)
{
	ALOGD("\n");
	if (strcmp(argv[0], "tinytest")) {
		fprintf(stderr, "\nExample: xxxtest -D 1 -d 0 -r 64000 -c 2 -b 24 -reboot 3 -repeat 5 -time 10\n");
		exit(-1);
	}

	if (argc < 5) {
		fprintf(stderr, "Usage: %s [-D card] [-d device] [-c channels] "
			"[-r rate] [-b bits] [-p period_size] [-n n_periods]"
			"[-reboot xxx] [-repeat xxx] [-time xxx]\n", argv[0]);
		ALOGD
		    ("\nExample: tinytest -D 1 -d 0 -r 64000 -c 2 -b 24 -reboot 3 -repeat 5 -time 10\n");
		exit(-1);
	}

}

static void getenv_from_cmdline(int argc, char **argv)
{
	ALOGD("\n");
	argv += 1;
	while (*argv) {
		if (strcmp(*argv, "-d") == 0) {
			argv++;
			if (*argv)
				device = atoi(*argv);
		} else if (strcmp(*argv, "-c") == 0) {
			argv++;
			if (*argv)
				channels = atoi(*argv);
		} else if (strcmp(*argv, "-r") == 0) {
			argv++;
			if (*argv)
				rate = atoi(*argv);
		} else if (strcmp(*argv, "-b") == 0) {
			argv++;
			if (*argv)
				bits = atoi(*argv);
		} else if (strcmp(*argv, "-D") == 0) {
			argv++;
			if (*argv)
				card = atoi(*argv);
		} else if (strcmp(*argv, "-p") == 0) {
			argv++;
			if (*argv)
				period_size = atoi(*argv);
		} else if (strcmp(*argv, "-n") == 0) {
			argv++;
			if (*argv)
				period_count = atoi(*argv);
		} else if (strcmp(*argv, "-reboot") == 0) {
			argv++;
			if (*argv)
				reboot_times = atoi(*argv);
		} else if (strcmp(*argv, "-repeat") == 0) {
			argv++;
			if (*argv)
				repeat_times = atoi(*argv);
		} else if (strcmp(*argv, "-time") == 0) {
			argv++;
			if (*argv)
				times_seconds = atoi(*argv);
		}
		if (*argv)
			argv++;
	}

}

static bool get_runtime_env(int argc, char **argv)
{
	char tmp[PROPERTY_VALUE_MAX];
	int ret = -1;
	ALOGD("\n");
	/* get environment from property, check fisrt boot */
	ret = property_get("persist.sys.capsrv_start", tmp, NULL);
	if (ret) {
		ALOGD("not first boot, getenv_from_property\n");
		getenv_from_property();
		return false;
	} else {
		ALOGD("first boot, getenv_from_cmdline\n");
		check_cmdline(argc, argv);
		getenv_from_cmdline(argc, argv);
		return true;
	}
}

static void backup_dynomic_env()
{
	char tmp[PROPERTY_VALUE_MAX];

	ALOGD("\n");
	raw_reboot = reboot_times;
	raw_repeat = repeat_times;

	sprintf(tmp, "%d", raw_reboot);
	property_set("persist.sys.raw_reboot", tmp);

	sprintf(tmp, "%d", raw_repeat);
	property_set("persist.sys.raw_repeat", tmp);
}

static void dump_backup_env()
{
	char tmp[PROPERTY_VALUE_MAX];
	int ret = -1;

	ALOGD("\n");
	property_get("persist.sys.raw_reboot", tmp, NULL);
	if (ret) {
		ALOGD("persist.sys.raw_reboot = %s\n", tmp);
	} else {
		//default
	}

	property_get("persist.sys.raw_repeat", tmp, NULL);
	if (ret) {
		ALOGD("persist.sys.raw_repeat = %s\n", tmp);
	} else {
		//default
	}

}

static void reverse_dynomic_env()
{
	ALOGD("\n");
	reboot_times = raw_reboot - reboot_times;	// 10 - 10 = 0;
	repeat_times = raw_repeat - repeat_times;
}

static int check_file_exists(const char *path)
{
	int ret = -1;

	ALOGD("\n");
	if (path == NULL) {
		ALOGD("check file is not exsit\n");
		return -1;
	}

	ret = access(path, F_OK);
	return ret;
}

static void wait_sdcard_mount(char *path)
{
	int retry = 1;

	/* when reboot, fuse service may be start later,  wait it a moment! */
	while (retry++) {
		if (check_file_exists(path) != 0) {
			ALOGE(" '%s' is not exsit, retry %d\n", path, retry);
			if (retry == 60) {
				exit(-1);
			}
			sleep(1);
		} else {
			ALOGD(" '%s' is exsit\n", path);
			break;
		}
	}

}

static void setup_report_dir()
{
	ALOGD("\n");
	if (check_file_exists(PCM_TEST) == 0) {
		ALOGD("check file is exsit, remove this dir\n");
		rm_dir(PCM_TEST);
	}

    /* first boot creat a new test dir */
	mkdir(PCM_TEST, 0755);
}

/* 0 ~ 9999 */
static char *creat_unit_index()
{
	unsigned int i = 0, j = 0;
	char reboot_dir[128], repeat_idx[264];
	char *repeat_rtn = NULL;
	unsigned int len;

	wait_sdcard_mount(PCM_TEST);
	
    ALOGD("creat_unit_index  stating ...\n");
	// /sdcard/pcm-test/reboot_0000
	sprintf(reboot_dir, "%s/reboot_%04d", PCM_TEST, reboot_times);
	mkdir(reboot_dir, 0766);

	ALOGD("reboot_dir is %s\n", reboot_dir);
	// /sdcard/pcm-test/reboot_0000/repeat_0000
	sprintf(repeat_idx, "%s/repeat_%04d", reboot_dir, repeat_times);
	ALOGD("repeat_idx is %s\n", repeat_idx);

	len = strlen(repeat_idx);
	repeat_rtn = (char *)malloc(len + 1);
	memset(repeat_rtn, 0x0, len + 1);
	memcpy(repeat_rtn, repeat_idx, len);

	ALOGD("repeat_rtn is %s\n", repeat_rtn);
	return repeat_rtn;
}

static void restore_backup_env()
{
	char tmp[PROPERTY_VALUE_MAX];
	int ret = -1;

	ALOGD("\n");
	property_get("persist.sys.raw_reboot", tmp, NULL);
	if (ret) {
		raw_reboot = atoi(tmp);
	} else {
		//default
	}

	property_get("persist.sys.raw_repeat", tmp, NULL);
	if (ret) {
		raw_repeat = atoi(tmp);
	} else {
		//default
	}
}

static void global_environment_init(int argc, char **argv)
{
	bool first_boot = true;

	ALOGD("\n");
	first_boot = get_runtime_env(argc, argv);
	if (first_boot) {
		backup_dynomic_env();
		dump_backup_env();
		reverse_dynomic_env();
		setup_report_dir();
	}
	restore_backup_env();

	ALOGD("reboot_times: %d, repeat_times: %d, times_seconds: %d\n"
		 "raw_reboot: %d, raw_repeat: %d",
		 reboot_times, repeat_times, times_seconds, raw_reboot,
		 raw_repeat);
}

void sigint_handler(int sig)
{
	ALOGD("\n");

	/* reset global environment */
	property_set("persist.sys.capsrv_start", NULL);
	capturing = 0;
	exit(0);
}

#if 0
void start_timing()
{
	ALOGD("\n");
	gettimeofday(&tpstart, NULL);
}

void end_timming()
{
	gettimeofday(&tpend, NULL);
	timeuse = 1000000 * (tpend.tv_sec - tpstart.tv_sec) +
	    (tpend.tv_usec - tpstart.tv_usec);
	ALOGD("spend Time %f\n", timeuse);
}
#endif

static void listen_timeout(void *param)
{
	char tmp[PROPERTY_VALUE_MAX];
    int ret = 0;

	ret = property_get("persist.sys.raw_reboot", tmp, NULL);

	if (!ret && *(int *)param < 5) {
		exit(-1);
	}

    sleep(5); //make sure main pthread go fisrt and has play wav file a moment.
	while (1) {
		ALOGD("timeuse = %d\n", timeuse);
		pthread_mutex_lock(&timeout_lock);
		if (timeuse >= (int)times_seconds || timeuse == -1) {
			ALOGD
			    ("Now timeout, stop capturing \n");
			capturing = 0;
			pthread_cond_wait(&pcap_start, &timeout_lock);
		}
        /* FIXME should lock wait capture also */
		timeuse++;
		sleep(0x1);
		pthread_mutex_unlock(&timeout_lock);
	}
}

unsigned int capture_sample(FILE * file, unsigned int card, unsigned int device,
			    unsigned int channels, unsigned int rate,
			    enum pcm_format format, unsigned int period_size,
			    unsigned int period_count)
{
	struct pcm_config config;
	struct pcm *pcm;
	char *buffer;
	unsigned int size;
	unsigned int bytes_read = 0;

	config.channels = channels;
	config.rate = rate;
	config.period_size = period_size;
	config.period_count = period_count;
	config.format = format;
	config.start_threshold = 0;
	config.stop_threshold = 0;
	config.silence_threshold = 0;

	pcm = pcm_open(card, device, PCM_IN, &config);
	if (!pcm || !pcm_is_ready(pcm)) {
		fprintf(stderr, "Unable to open PCM device (%s)\n",
			pcm_get_error(pcm));
		return 0;
	}

	size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
	buffer = malloc(size);
	if (!buffer) {
		fprintf(stderr, "Unable to allocate %d bytes\n", size);
		free(buffer);
		pcm_close(pcm);
		return 0;
	}

	ALOGD("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,
	       pcm_format_to_bits(format));

	while (capturing && !pcm_read_ex(pcm, buffer, size)) {
		if (fwrite(buffer, 1, size, file) != size) {
			fprintf(stderr, "Error capturing sample\n");
			break;
		}
		bytes_read += size;
	}

	free(buffer);
	pcm_close(pcm);
	return pcm_bytes_to_frames(pcm, bytes_read);
}

static int capture_wav(unsigned int frames, enum pcm_format format,
		       char *repeat_idx)
{
	ALOGD("\n");
	struct wav_header header;
	FILE *file;
	char path[256] = { 0 };
	char *file_fmt = ".wav";

	header.riff_id = ID_RIFF;
	header.riff_sz = 0;
	header.riff_fmt = ID_WAVE;
	header.fmt_id = ID_FMT;
	header.fmt_sz = 16;
	header.audio_format = FORMAT_PCM;
	header.num_channels = channels;
	header.sample_rate = rate;

	sprintf(path, "%s%s", repeat_idx, file_fmt);
	file = fopen(path, "wb+");
	if (!file) {
		fprintf(stderr, "Unable to create file '%s'\n", path);
		return -1;
	}

	switch (bits) {
	case 32:
		format = PCM_FORMAT_S32_LE;
		break;
	case 24:
		format = PCM_FORMAT_S24_LE;
		break;
	case 16:
		format = PCM_FORMAT_S16_LE;
		break;
	default:
		fprintf(stderr, "%d bits is not supported.\n", bits);
		return 1;
	}

	header.bits_per_sample = pcm_format_to_bits(format);
	header.byte_rate = (header.bits_per_sample / 8) * channels * rate;
	header.block_align = channels * (header.bits_per_sample / 8);
	header.data_id = ID_DATA;

	/* leave enough room for header */
	fseek(file, sizeof(struct wav_header), SEEK_SET);

	frames = capture_sample(file, card, device, header.num_channels,
				header.sample_rate, format,
				period_size, period_count);
	ALOGD("Captured %d frames\n", frames);

	/* write header now all information is known */
	header.data_sz = frames * header.block_align;
	header.riff_sz = header.data_sz + sizeof(header) - 8;
	fseek(file, 0, SEEK_SET);
	fwrite(&header, sizeof(struct wav_header), 1, file);

	fclose(file);

	return 0;
}

static void analyze_capdata(enum pcm_format format,
			    unsigned int reboot_times,
			    unsigned int repeat_times,
			    unsigned int times_seconds)
{
	ALOGD("analyze_capdata [reboot %d] [repeat %d]...\n", reboot_times,
		 repeat_times);
}

static int current_runtime_exit()
{
	ALOGD("\n");

	if (repeat_times >= (raw_repeat - 1)) {
		ALOGD("REPEAT_OVER\n");
		return REPEAT_OVER;
	}

	if (repeat_times < (raw_repeat - 1)) {
		ALOGD("REPEAT_CONTINUE\n");
		return REPEAT_CONTINUE;
	}

	ALOGD("we can not go here\n");

	return -1;
}

int main(int argc, char **argv)
{
	unsigned int frames;
	enum pcm_format format;
	int ret = -1;
	pthread_t pidl;
	char *repeat_idx = NULL;
	pid_t child;
	int status;
	pid_t pid;
	char *exec_args[] = {"playtest", "/sdcard/hetu.wav", "-D", "2", "-d", "0", 0};

    ALOGD("----hello world----\n");
	/*ensure boot env */
	global_environment_init(argc, argv);
#ifdef AEC_PLAY
	/* for AEC test, play a wav file by itself */
	child = fork();
	if (child < 0) {
		ALOGE("error: AEC fork failed\n");
		return 0;
	}
	if (child == 0) {
		ALOGD("for AEC test, play a wav file by itself\n");
		wait_sdcard_mount("/sdcard/hetu.wav");
		sleep(5);
		if (execvp(exec_args[0], exec_args) < 0) {
			int saved_errno = errno;
			ALOGE("exec failed for 'playtest' Error:%s\n",
					strerror(errno));
			return -saved_errno;
		}
	} //TODO wait pid child
#endif
	/* install signal handler and begin capturing */
	signal(SIGINT, sigint_handler);

    /* creat a pthreat to listen timeout, then send a signal */
    pthread_create(&pidl, NULL, (void *)listen_timeout, &argc);

    while (1) {
        /*fist start*/
        if (timeuse == -1) {
            timeuse = 0;
            pthread_cond_signal(&pcap_start);
        }
        /*result addr */
        repeat_idx = creat_unit_index();

        /* capture raw data wav format */
        capture_wav(frames, format, repeat_idx);
        if (!repeat_idx) {
            free(repeat_idx);
        }

        /* analyze the wav data */
		analyze_capdata(format, reboot_times, repeat_times,
				times_seconds);

		//if times not over, set property and kill selfPid
		ret = current_runtime_exit();
		switch (ret) {
		case REPEAT_OVER:
			{
				reboot_times++;
				repeat_times = 0;	//FIXME no use
				property_set("persist.sys.capsrv_start", "1");
				setenv_to_property();
			}
			break;
		case REPEAT_CONTINUE:
			{
				repeat_times++;
				timeuse = 0;
				capturing = 1;
				pthread_cond_signal(&pcap_start);
			}
			break;
		default:
			break;
		}

		/* check reboot ? */
		if (ret == REPEAT_OVER) {
            if (reboot_times >= raw_reboot) {
                ALOGD("TEST_OVER\n");
				property_set("persist.sys.capsrv_start", "");
#ifdef AEC_PLAY
				if (kill (child, SIGSTOP) < 0) {
					ALOGE("kill child failed\n");
					while ((pid=waitpid(-1, &status, 0)) != child) {
						if (pid == -1) {
							ALOGE("error: AEC waitpid failed!\n");
							return 1;
						}
					}
				}
#endif
                exit(0);
            }
			sync();
			reboot(RB_AUTOBOOT);
			break;
		}
//        start_timing();
	}

    ALOGD("----goodbye world----\n");

	return 0;
}
